genQC logo genQC
  • Overview
  • Get Started
  • Tutorials
  • API Reference
  • Research
  • Code Repository
  1. Discrete-continuous circuits with multimodal diffusion
  2. Quantum Fourier transform and gate-pair tokenization

Tutorials

  • Tutorials Overview

  • Discrete-continuous circuits with multimodal diffusion
    • Compile unitaries with parametrized circuits
    • Quantum Fourier transform and gate-pair tokenization
  • Quantum circuit synthesis with diffusion models
    • Generate a circuit
    • Editing and masking of circuits
    • Compile unitaries
    • SRV demo-dataset and fine-tune

On this page

  • Load model
    • Set inference parameters
  • Compile the QFT unitary
    • Evaluate and plot circuits
  • Gate-Pair tokenization
  • Report an issue
  • View source
  1. Discrete-continuous circuits with multimodal diffusion
  2. Quantum Fourier transform and gate-pair tokenization

Quantum Fourier transform and gate-pair tokenization

Unitary compilation
Parameterized gates
Quantum circuits
Pretrained model
A short tutorial showing the compilation of the Quantum Fourier transform (QFT) and extracting tokens via Gate-Pair tokenization (GPE).
from genQC.imports import *
import genQC.utils.misc_utils as util

from genQC.dataset.config_dataset import ConfigDataset
from genQC.pipeline.multimodal_diffusion_pipeline import MultimodalDiffusionPipeline_ParametrizedCompilation
from genQC.scheduler.scheduler_dpm import DPMScheduler

from genQC.platform.tokenizer.circuits_tokenizer import CircuitTokenizer
from genQC.platform.simulation import Simulator, CircuitBackendType
from genQC.inference.sampling import decode_tensors_to_backend, generate_compilation_tensors
from genQC.inference.evaluation_helper import get_unitaries
from genQC.inference.eval_metrics import UnitaryInfidelityNorm
from genQC.benchmark.bench_compilation import SpecialUnitaries
import genQC.platform.tokenizer.tensor_tokenizer as gpe
util.MemoryCleaner.purge_mem()      # clean existing memory alloc
device = util.infer_torch_device()  # use cuda if we can
device
[INFO]: Cuda device has a capability of 8.6 (>= 8), allowing tf32 matmul.
device(type='cuda')
# We set a seed to pytorch, numpy and python. 
# Note: This will also set deterministic algorithms, possibly at the cost of reduced performance!
util.set_seed(0)

Load model

Load the pre-trained model directly from Hugging Face: Floki00/cirdit_multimodal_compile_3to5qubit.

pipeline = MultimodalDiffusionPipeline_ParametrizedCompilation.from_pretrained("Floki00/cirdit_multimodal_compile_3to5qubit", device)

The model is trained with the gate set:

pipeline.gate_pool
['h', 'cx', 'ccx', 'swap', 'rx', 'ry', 'rz', 'cp']

which we need in order to define the vocabulary, allowing us to decode tokenized circuits.

vocabulary = {g:i+1 for i, g in enumerate(pipeline.gate_pool)} 
tokenizer  = CircuitTokenizer(vocabulary)
tokenizer.vocabulary
{'h': 1, 'cx': 2, 'ccx': 3, 'swap': 4, 'rx': 5, 'ry': 6, 'rz': 7, 'cp': 8}

Set inference parameters

Set diffusion model inference parameters.

pipeline.scheduler   = DPMScheduler.from_scheduler(pipeline.scheduler)
pipeline.scheduler_w = DPMScheduler.from_scheduler(pipeline.scheduler_w)

timesteps = 40
pipeline.scheduler.set_timesteps(timesteps) 
pipeline.scheduler_w.set_timesteps(timesteps) 

pipeline.lambda_h = 1.5
pipeline.lambda_w = 0.45
pipeline.g_h = 0.4
pipeline.g_w = 0.2

# These parameters are specific to our pre-trained model.
system_size   = 5
max_gates     = 32

For evaluation, we also need a circuit simulator backend.

simulator = Simulator(CircuitBackendType.QISKIT)

Compile the QFT unitary

We now compile the 4-qubit QFT.

samples       = 512
num_of_qubits = 4
prompt        = f"Compile {num_of_qubits} qubits using: ['h', 'cx', 'ccx', 'swap', 'rx', 'ry', 'rz', 'cp']"

U = SpecialUnitaries.QFT(num_of_qubits).to(torch.complex64)
out_tensor, params = generate_compilation_tensors(pipeline, 
                                      prompt=prompt, 
                                      U=U, 
                                      samples=samples, 
                                      system_size=system_size, 
                                      num_of_qubits=num_of_qubits, 
                                      max_gates=max_gates,
                                      no_bar=False,        # show progress bar
                                      auto_batch_size=256, # limit batch size for less GPU memory usage
                                     )
[INFO]: (generate_comp_tensors) Generated 512 tensors

For instance, a circuit tensor alongside parameters the model generated looks like this

print(out_tensor[0])
print(params[0])
tensor([[ 0,  0,  8,  0, -2,  0,  0,  0,  4,  0,  0,  0,  0,  0,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9],
        [ 0,  0,  0,  4,  2,  3,  1,  8,  0,  8,  8,  0,  0,  0,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9],
        [ 0,  8,  8,  4,  0, -3,  0,  0,  0,  0,  8,  1,  8,  0,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9],
        [ 1,  8,  0,  0,  0, -3,  0,  8,  4,  8,  0,  0,  8,  1,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9,  9]], device='cuda:0')
tensor([[ 0.0000, -0.7021, -0.9835,  0.0000,  0.0000,  0.0000,  0.0000, -0.9720,  0.0000,  0.6553,  0.2625,  0.0000, -0.7555,  0.0000,  0.0000,  0.0000,  0.0000,  0.0000,  0.0000,  0.0000,  0.0000,
          0.0000,  0.0000,  0.0000,  0.0000,  0.0000,  0.0000,  0.0000,  0.0000,  0.0000,  0.0000,  0.0000]], device='cuda:0')

Evaluate and plot circuits

We decode these now to circuits and calculate their unitaries.

generated_qc_list, _ = decode_tensors_to_backend(simulator, tokenizer, out_tensor, params)
generated_us         = get_unitaries(simulator, generated_qc_list)

We then evaluate the unitary infidelity to our target U.

U_norms = UnitaryInfidelityNorm.distance(
                    approx_U=torch.from_numpy(np.stack(generated_us)).to(torch.complex128), 
                    target_U=U.unsqueeze(0).to(torch.complex128),
                )

We get the following distribution of the infidelities.

plt.figure(figsize=(7, 3), constrained_layout=True)
plt.xlabel(UnitaryInfidelityNorm.name(), fontsize=13)
plt.ylabel("Frequency", fontsize=13)
plt.hist(U_norms, bins=60)
plt.xlim([-0.05, 1.05])
plt.show()

We plot the four best ciruits, w.r.t. the infidelity:

plot_k_best = 4

idx = np.argsort(U_norms)
fig, axs = plt.subplots(1, plot_k_best, figsize=(10, 2), constrained_layout=True, dpi=150)

for i, (idx_i, ax) in enumerate(zip(idx[:plot_k_best], axs.flatten())): 
    ax.clear()
    generated_qc_list[idx_i].draw("mpl", plot_barriers=False, ax=ax)
    ax.set_title(f"The {i+1}. best circuit: \n infidelity {U_norms[idx_i]:0.1e}.", fontsize=10)

Gate-Pair tokenization

Now we want to extract reusable substructures (gadgets) from generated circuits. We use all generated tensors in out_tensor, regardless if their circuits have good or bad infidelity.

gate_pair_tokenizer = gpe.GatePairTokenizer(unique_class_values=pipeline.embedder.unique_class_values, 
                                            zero_token=0, 
                                            padding_token=9, 
                                            device="cpu")

Next, we run our proposed Gate-Pair Encoding (GPE) scheme:

_ = gate_pair_tokenizer.learn(out_tensor.cpu(), max_depth=5, max_iters=100)
New depth reached 1
New depth reached 2
New depth reached 3
break: max_iters reached

Now we plot the extracted tokens.

max_depth = 4
topk      = 5
unpacked_vocab_configs_depths, unpacked_vocab_configs_cnts_depths = \
                    gpe.get_topk_depth_unpacked(gate_pair_tokenizer, num_of_qubits, use_raw=True)
max_depth = min(max_depth, max(unpacked_vocab_configs_depths.keys()))
fig, axs = plt.subplots(max_depth, topk, figsize=(12, 6), dpi=200)

for ax in axs.flatten():
    ax.clear()   
    ax.set_axis_off()

for (depth, unpacked_vocab_configs), (unpacked_vocab_configs_cnts), axs_sel in \
        zip(unpacked_vocab_configs_depths.items(), unpacked_vocab_configs_cnts_depths.values(), axs):
        
    if depth > max_depth:
        break
   
    for i, (ax, unpacked_vocab_config, unpacked_vocab_config_cnt) in \
            enumerate(zip(axs_sel, unpacked_vocab_configs, unpacked_vocab_configs_cnts)):
        
        zero_ps = torch.zeros((1, unpacked_vocab_config.shape[-1])) - 1
        instr = tokenizer.decode(unpacked_vocab_config, zero_ps)
        qc    = simulator.genqc_to_backend(instr, place_barriers=False)

        #------

        ax.clear() 
        qc.draw("mpl", 
                plot_barriers=False, 
                ax=ax, 
                idle_wires=False)

        for text in ax.texts:
            if 'q' in text.get_text():
                text.set_visible(False)
                text.remove()

        ax.patch.set_facecolor('none')
        ax.patches[0].set_color("none")
                
        ax.set_title(f"Occurrences: {unpacked_vocab_config_cnt.item()}", fontsize=6)
        if i==0:
            plt.figtext(-0.03, 1-(depth-0.7)/max_depth, f"Depth {depth}:", horizontalalignment='left', verticalalignment='top', fontsize=12)

plt.tight_layout()
plt.show()

As we only extract discrete tokens, the parameters of the continuous gates are set to 0 for plotting.

import genQC
print("genQC Version", genQC.__version__)
genQC Version 0.2.0
Back to top
Compile unitaries with parametrized circuits
Generate a circuit
 

Copyright 2025, Florian Fürrutter

  • Report an issue
  • View source